#include "server.h"
#include "user.h"

#include "db/database.h"

#include "tuimp/faketcp.h"
#include "tuimp/serverprotocol.h"
#include "tuimp/serverprotocolaction.h"


//USER_TIMEOUT is defined to 5 minutes.
#define USER_TIMEOUT 300000

Server::Server(quint16 port) : QObject(), port(port) {
	db = new Database("tuim.db");
	Friend *f;
	foreach (f, db->all()){
		users[f->getId()]=f;
	}
	listenSocket = new FakeTcp(QString(),0,true,port);
	serverProtocol = new ServerProtocol(listenSocket);
	keepAliveTimer = new QTimer(this);
	QObject::connect(keepAliveTimer, SIGNAL(timeout()), this, SLOT(checkKeepAlive()));
	keepAliveTimer->start(USER_TIMEOUT);

	QObject::connect(serverProtocol,SIGNAL(readyAction()),this,SLOT(action()));
	nextChatId = 0;
}

Server::~Server() {
	delete keepAliveTimer;
	delete db;
	delete listenSocket;
	delete serverProtocol;
}

void Server::action() {
	ServerProtocolAction *spa = serverProtocol->getAction();
	switch (spa->getType()) {
		case TConnection: {
			ServerConnectionAction *sca = static_cast<ServerConnectionAction *>(spa);
			connectionAction(sca);
			break;
		}
		case TChat: {
			ServerChatAction *sha = static_cast<ServerChatAction *>(spa);
			chatAction(sha);
			break;
		}
		case TUser: {
			ServerUserAction *sua = static_cast<ServerUserAction *>(spa);
			userAction(sua);
			break;
		}
		case TFriend: {
			ServerFriendAction *sfa = static_cast<ServerFriendAction *>(spa);
			friendAction(sfa);
			break;
		}
	}
}

void Server::checkKeepAlive() {
	const int maxTime = USER_TIMEOUT;

	QMap<Friend*, QTime*>::iterator i;
	for(i = aliveTimes.begin(); i != aliveTimes.end(); i++) {
		QTime* time = i.value();
		if(time->elapsed() > maxTime) {
			Friend* user = i.key();
			disconnect(user);
		}
	}
}

void Server::connectionAction(ServerConnectionAction* sca) {
	QPair<QString, quint16> userInfo = qMakePair(sca->getIp(), sca->getPort());
	switch(sca->getFunction()){
		case CFConnect:
			connect(userInfo, sca->getLogin(), sca->getPassword());
			break;
		case CFDisconnect:
			if(who.contains(userInfo))
				disconnect(who[userInfo]);
			break;
		case CFAnswerKeepAlive: {
			if(who.contains(userInfo))
				keepAlive(who[userInfo]);
			break;
		}
	}
}

void Server::chatAction(ServerChatAction* sha) {
	QPair<QString, quint16> userInfo = qMakePair(sha->getIp(), sha->getPort());
	switch(sha->getFunction()) {
		case HFStart:
			if(who.contains(userInfo)) {
				Friend* user = who[userInfo];
				chatStart(user, sha->getId());
			}
			break;
		case HFInvite:
			if(who.contains(userInfo)) {
				Friend* user = who[userInfo];
				chatInvite(user, sha->getChat(), sha->getId());
			}
			break;
		case HFSend:
			if(who.contains(userInfo)) {
				Friend* user = who[userInfo];
				chatSend(user, sha->getChat(), sha->getMessage());
			}
			break;
		case HFExit:
			if(who.contains(userInfo)) {
				Friend* user = who[userInfo];
				chatExit(user, sha->getChat());
			}
			break;
	}
}

void Server::userAction(ServerUserAction* sua) {
	QPair<QString, quint16> userInfo = qMakePair(sua->getIp(), sua->getPort());
	switch(sua->getFunction()) {
		case UFPassword: {
			if(who.contains(userInfo)) {
				Friend* user = who[userInfo];
				ServerUserProtocol::changePassword(where[user], db->setPassword(user->getId(), sua->getOldPassword(), sua->getNewPassword()));
			}
			break;
		}
		case UFStatus: {
			if(who.contains(userInfo)) {
				Friend* user = who[userInfo];
				user->setStatus(sua->getNewStatus());
				QList<int> friendIds = db->getFriends(user->getId());
				int id;
				foreach(id, friendIds) {
					if(where.contains(users[id])){
						ServerUserProtocol::friendStatus(where[users[id]],user);
					}
				}
			}
			break;
		}
		case UFSignup: {
			QString login = sua->getLogin();
			QString password = sua->getPassword();
			bool result = db->setUser(login, password);
			User* user = db->getUser(login, password);
			if(!user) break;
			if(result) users[user->getId()] = new Friend(user->getId(), Offline, login);
			
			connect(userInfo, login, password);
			break;
		}
	}
}

void Server::friendAction(ServerFriendAction* sfa) {
	QPair<QString, quint16> userInfo = qMakePair(sfa->getIp(), sfa->getPort());
	switch(sfa->getFunction()) {
		case FFInvite:
			if(who.contains(userInfo)) {
				Friend* user = who[userInfo];
				friendInvite(user, sfa->getLogin());
			}
			break;
		case FFAccept:
			if(who.contains(userInfo)) {
				Friend* user = who[userInfo];
				friendAccept(user, sfa->getLogin());
			}
			break;
		case FFDeny:
			if(who.contains(userInfo)) {
				Friend* user = who[userInfo];
				friendDeny(user, sfa->getLogin());
			}
			break;
		case FFRemove:
			if(who.contains(userInfo)) {
				Friend* user = who[userInfo];
				friendRemove(user, sfa->getFriend());
			}
			break;
	}
}

void Server::connect(QPair<QString, quint16> userInfo, QString login, QString password) {
	/* Search user and password in the database */
	QString userIp = userInfo.first;
	quint16 userPort = userInfo.second;
	FakeTcp *client = new FakeTcp(userIp, userPort,false,port);
	User *user=db->getUser(login, password);
	if(user) {
		Friend *clientUser=users[user->getId()];
		if(!where.contains(clientUser)) {
			who[qMakePair(userIp, userPort)]=clientUser;
			where[clientUser]=client;
			QTime* userTime = new QTime();
			userTime->start();
			aliveTimes[clientUser] = userTime;
			clientUser->setStatus(Online);
			QList<Friend *> friends;
			QList<int> friendIds = db->getFriends(user->getId());
			int id;
			foreach(id, friendIds) {
				friends.push_back(users[id]);
				if(where.contains(users[id])){
					ServerUserProtocol::friendStatus(where[users[id]],clientUser);
				}
			}
			ServerConnectionProtocol::accept(client, clientUser);
			ServerUserProtocol::friendList(client,friends);
			QList<int> invitations = db->getInvitations(user->getId());
			foreach(id, invitations) {
				ServerFriendProtocol::invite(client, users[id]->getName());
			}
		}
		else {
		ServerConnectionProtocol::deny(client);
		}
	}
	else {
		ServerConnectionProtocol::deny(client);
	}
}

void Server::disconnect(Friend* user) {
	user->setStatus(Offline);
	QList<int> friendIds = db->getFriends(user->getId());
	QList<int> chatsIds = user->getChats();
	int id;
	foreach(id, friendIds) {
		if(where.contains(users[id])){
			ServerUserProtocol::friendStatus(where[users[id]],user);
		}
	}
	foreach(id, chatsIds) {
		if(chats.contains(id)) chatExit(user, chats[id]);
	}
	FakeTcp* clientSocket = where.take(user);
	who.take(qMakePair(clientSocket->getDestination(), clientSocket->getPort()));
	delete aliveTimes.take(user);
	delete clientSocket;
}

void Server::keepAlive(Friend* user) {
	aliveTimes[user]->restart();
}

void Server::chatStart(Friend* user, int friendId) {
	if(!where.contains(users[friendId])) return;
	QList<int> friendIds = db->getFriends(user->getId());
	if(!friendIds.contains(friendId)) return;
	Friend* f = users[friendId];

	QList<Friend*> chatFriends;
	chatFriends.push_back(user);
	chatFriends.push_back(f);

	chatMutex.lock();
	while(chats.contains(nextChatId)) nextChatId++;
	Chat* chat = new Chat(nextChatId,chatFriends);
	chats.insert(nextChatId, chat);
	nextChatId++;
	chatMutex.unlock();

	user->addChat(chat->getId());
	f->addChat(chat->getId());

	ServerChatProtocol::start(where[user], chat);
	ServerChatProtocol::start(where[f], chat);
}

void Server::chatInvite(Friend* user, Chat* _chat, int friendId) {
	if(!where.contains(users[friendId])) return;
	QList<int> friendIds = db->getFriends(user->getId());
	if(!friendIds.contains(friendId)) return;
	Friend* f = users[friendId];

	int chatId = _chat->getId();
	chatMutex.lock();
	Chat* chat = chats[chatId];
	QList<Friend*> friends = chat->getFriends();
	QList<int> fFriends = db->getFriends(f->getId());

	foreach(Friend* current, friends) {
		if(!fFriends.contains(current->getId())) return;
	}

	if(!friends.contains(user)) return;
	if(friends.contains(f)) return;
	chat->addFriend(f);

	chatMutex.unlock();

	ServerChatProtocol::start(where[f], chat);
	Friend* current;
	foreach(current, friends) {
		if(f != current) ServerChatProtocol::enter(where[current], chat, f);
	}
}

void Server::chatSend(Friend* user, Chat* _chat, QString message) {
	int id = _chat->getId();
	chatMutex.lock();
	Chat* chat = chats[id];
	QList<Friend*> friends = chat->getFriends();
	chatMutex.unlock();
	
	if(!friends.contains(user)) return;
	Friend *f;
	foreach(f, friends) {
		if(f != user)	ServerChatProtocol::send(where[f], chat, user, message);
	}
}

void Server::chatExit(Friend* user, Chat* _chat) {
	int id = _chat->getId();
	user->removeChat(id);

	chatMutex.lock();
	Chat* chat = chats[id];
	chat->removeFriend(user);
	QList<Friend*> friends = chat->getFriends();
	chatMutex.unlock();

	Friend *f;
	if(friends.size() == 1) {
		ServerChatProtocol::terminate(where[friends[0]], chat);
		delete chats.take(id);
	} else {
		foreach(f, friends) {
			ServerChatProtocol::exit(where[f], chat, user);
		}
	}
}

void Server::friendInvite(Friend* user, QString login) {
	int otherId = db->getUserId(login);
	Friend* f = users[otherId];
	
	if(db->inviteFriend(user->getId(), otherId)){
		if(!where.contains(f)) return;
		ServerFriendProtocol::invite(where[f], user->getName());
	}
}

void Server::friendAccept(Friend* user, QString login) {
	int otherId = db->getUserId(login);
	Friend* f = users[otherId];
	
	if(db->acceptInvitation(otherId, user->getId())){
		ServerFriendProtocol::add(where[user], f);
		if(!where.contains(f)) return;
		ServerFriendProtocol::add(where[f], user);
	}
}

void Server::friendDeny(Friend* user, QString login) {
	int otherId = db->getUserId(login);
	Friend* f = users[otherId];
	
	if(db->refuseInvitation(otherId, user->getId())){
		if(!where.contains(f)) return;
		ServerFriendProtocol::deny(where[f], login);
	}
}

void Server::friendRemove(Friend* user, Friend* other) {
	Friend* f = users[other->getId()];
	
	if(db->deleteFriend(user->getName(), f->getName())){
		ServerFriendProtocol::remove(where[user], f);
		if(!where.contains(f)) return;
		ServerFriendProtocol::remove(where[f], user);
	}
}
